/*
 * http_conn.c
 *
 *  Created on: 2015-5-15
 *      Author: likui
 */


#include "http_conn.h"
#include "error.h"

#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/mman.h>
#include <stdio.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/epoll.h>

extern int user_count;

/**
 * @brief 设置文件描述符属性
 * @param fd 文件描述符
 * @param flags 需要设置的文件描述符标志
 */
void set_fl(int fd, int flags)
{
	int		val;

	// 获取标志
	if ((val = fcntl(fd, F_GETFL, 0)) < 0) {
		error_ret("fcntl get error");
	}

	val |= flags;

	// 设置标志
	if (fcntl(fd, F_SETFL, val) < 0) {
		error_ret("fcntl set error");
	}
}

/**
 * @brief 添加描述符fd到epollfd
 * @param epollfd epoll文件描述符
 * @param fd 文件描述符
 * @param one_shut 只注册一次
 */
void epoll_add(int epollfd, int fd, bool one_shot)
{
	struct epoll_event	event = {0};

	// 数据可读 | ET | TCP连接被对方关闭，或者对方关闭了写操作
	event.data.fd = fd;
	event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;

	if (one_shot) {
		event.events |= EPOLLONESHOT;
	}

	// 操作事件表
	if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) != 0) {
		error_ret("epoll_ctl add error");
	}

	// 设置为非阻塞
	set_fl(fd, O_NONBLOCK);
}

/**
 * @brief 将文件描述符fd从epollfd移出
 * @param epollfd epoll文件描述符
 * @param fd 文件描述符
 */
void epoll_rm(int epollfd, int fd)
{
	if (epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0) != 0) {
		error_ret("epoll_ctl del error");
	}

	if (close(fd) != 0) {
		error_ret("close error");
	}
}

/**
 * @brief 修改文件描描述符fd在epollfd中的属性
 * @param epollfd epoll文件描述符
 * @param fd 文件描述符
 * @param ev 事件
 */
void epoll_mod(int epollfd, int fd, int ev)
{
	struct epoll_event	event = {0};

	// ev | ET | 只触发一个可读、可写或异常事件，且只触发一次
	event.data.fd = fd;
	event.events = ev | EPOLLET | EPOLLONESHOT  | EPOLLRDHUP;

	// 操作事件表
	if (epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event) != 0) {
		error_ret("epoll_ctl error");
	}
}

/**
 * @brief 关闭连接
 * @param http_conn http_conn_t结构指针
 * @param real_close 是真关闭
 * @param epollfd epoll文件描述符
 */
void http_conn_close_conn(http_conn_t *http_conn, bool real_close, int epollfd)
{
	if (real_close && (http_conn->sockfd != -1) ) {
		epoll_rm(epollfd, http_conn->sockfd);
		http_conn->sockfd = -1;
		user_count --;
	}
}

/**
 * @brief 初始化http_conn_t 结构
 * @param http_conn http_conn_t结构
 * @param sockfd 套接字
 * @param addr 套接字地址结构
 * @param addrlen 套接字地址结构长度
 * @param epollfd epoll文件描述符
 */
void http_conn_init(http_conn_t *http_conn,int sockfd, const struct sockaddr *addr, socklen_t addrlen, int epollfd)
{
	int		reuse = 1;

	http_conn->sockfd = sockfd;

	if (setsockopt(http_conn->sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) != 0) {
		error_ret("setsockopt error");
	}
	epoll_add(epollfd, sockfd, true);

	http_conn->peer_address = *addr;
	http_conn->peer_addrlen = addrlen;

	http_conn->timer = NULL;			// 定时器结构指针

	user_count ++;

	http_conn_init_part(http_conn);
}

/**
 * @brief 保持连接时需要重新初始化的部分结构
 * @param http_con http_conn_t结构指针
 */
void http_conn_init_part(http_conn_t *http_conn)
{
	// 读缓冲
	memset(http_conn->read_buf, '\0', READ_BUFFER_SIZE);
	http_conn->rd_read_index = 0;
	http_conn->rd_check_index = 0;
	http_conn->rd_start_line = 0;

	// 写缓冲
	memset(http_conn->write_buf, '\0', WRITE_BUFFER_SIZE);
	http_conn->wr_length = 0;

	// 状态
	http_conn->check_state = CHECK_STATE_REQUESTLINE;

	// HTTP 信息
	http_conn->http_method = GET;
	http_conn->http_url = NULL;
	http_conn->http_version = NULL;
	http_conn->http_host = NULL;
	http_conn->http_content_len = 0;
	http_conn->http_linger = false;
	http_conn->http_file_address = NULL;

	memset(http_conn->http_filename, '\0', FILENAME_LEN);

    // 定时器
    http_conn->timer = NULL;
}

/**
 * @brief 获取一行的起始位置
 * @param http_conn http_conn_t结构指针
 * @return 返回一行的起始地址
 */
char * http_conn_get_line(http_conn_t *http_conn)
{
	return http_conn->read_buf + http_conn->rd_start_line;
}

/**
 * @brief 循环读取客户数据，直到无数据可读或者对方关闭连接
 * @param http_conn http_conn_t结构指针
 * @return 若成功则为true,若失败则为false
 */
bool http_conn_read(http_conn_t *http_conn)
{
#ifndef NODEBUG
	error_msg("http_conn_read start: %lx", pthread_self());
#endif
	int bytes_read = 0;

	if (http_conn->rd_read_index >= READ_BUFFER_SIZE) {
		return false;
	}

	while (true) {
		bytes_read = recv(http_conn->sockfd, http_conn->read_buf + http_conn->rd_read_index,
				READ_BUFFER_SIZE - http_conn->rd_read_index, 0);
		if (bytes_read == -1) {
			if (errno == EAGAIN || errno == EWOULDBLOCK) {
				break;
			}
			return false;

		} else if (bytes_read == 0) {
			return false;					// 对方关闭套接字、读到EOF、或者缓冲区满了
		}
		http_conn->rd_read_index += bytes_read;
	}

	return true;
}

/**
 * @brief 处理一行
 * @param http_conn http_conn_t结构指针
 * @return 返回行状态
 */
LINE_STATE http_conn_parse_line(http_conn_t *http_conn)
{
	char	*temp;

	// 待验证的字符串 < 已读入的最后一个字节的下一个位置
	for ( ; http_conn->rd_check_index < http_conn->rd_read_index; ++ http_conn->rd_check_index) {

		temp = &(http_conn->read_buf[http_conn->rd_check_index]);
		if (*temp == '\r') {

			if ((http_conn->rd_check_index + 1) == http_conn->rd_read_index) {
				// 数据不足

				return LINE_OPEN;
			} else if (http_conn->read_buf[http_conn->rd_check_index + 1] == '\n') {
				// 读到了\r\n，为完成的行，\r\n被替换成\0

				http_conn->read_buf[http_conn->rd_check_index ++ ] = '\0';
				http_conn->read_buf[http_conn->rd_check_index ++ ] = '\0';

				return LINE_OK;
			}

			return LINE_BAD;

		} else if (*temp == '\n') {

			if ( (http_conn->rd_check_index > 1) && (http_conn->read_buf[http_conn->rd_check_index - 1] == '\r') ) {
				// 读到了\r\n，为完成的行，\r\n被替换成\0

				http_conn->read_buf[http_conn->rd_check_index -1 ] = '\0';
				http_conn->read_buf[http_conn->rd_check_index ++ ] = '\0';

				return LINE_OK;
			}

			return LINE_BAD;
		}
	}

	return LINE_OPEN;		// 需要继续读取数据
}

/**
 * @brief 处理请求行
 * @param http_conn http_conn_t结构指针
 * @param text 读取的起始位置指针
 * @return 返回HTTP状态
 */
HTTP_CODE http_conn_parse_request_line(http_conn_t *http_conn, char *text)
{
	char *method, *ptr;

	/* URL */
	if ((ptr = strpbrk(text, " ")) == NULL) {		// 第一个空格字符
		return BAD_REQUEST;
	}
	*ptr++ = '\0';									// 分割字符串
	ptr += strspn(ptr, " ");						// 第一个非空格字符
	http_conn->http_url = ptr;						// URL

	/* 方法 */
	method = text;
	if (strcasecmp(method, "GET") == 0) {
		http_conn->http_method = GET;
	} else {
		return BAD_REQUEST;
	}

	// 版本号
	if ((ptr = strpbrk(ptr, " ")) == NULL) {		// 第一个空格字符
		return BAD_REQUEST;
	}
	*ptr++ = '\0';
	ptr += strspn(ptr, " ");						// 第一个非空格字符
	if (strcasecmp(ptr, "HTTP/1.1") != 0) {
		return BAD_REQUEST;
	}
	http_conn->http_version = ptr;

	// 去掉HTTP头部
	if (strncasecmp(http_conn->http_url, "http://", 7) == 0) {
		http_conn->http_url += 7;
		http_conn->http_url = strchr(http_conn->http_url, '/');
	}

	// URL 为空，或者不是以'/'开始的
	if ( !(http_conn->http_url) || (http_conn->http_url[0] != '/') ){
		return BAD_REQUEST;
	}

	// 下一步读取首部
	http_conn->check_state = CHECK_STATE_HEADER;

	return NO_REQUEST;
}

/**
 * @brief 处理首部
 * @param http_conn http_conn_t结构指针
 * @param text 起始位置指针
 * @return 返回HTTP状态
 */
HTTP_CODE http_conn_parse_headers(http_conn_t *http_conn, char *text)
{
	static string_t hd_Connection = STRING("Connection:");
	static string_t hd_Content_Length = STRING("Content-Length:");
	static string_t hd_Host = STRING("Host:");

	// 遇到空行，表示头部解析已经完成
	if (text[0] == '\0') {

		// 如果正文长度不为０，则表示还有正文待读取
		if (http_conn->http_content_len != 0) {
			http_conn->check_state = CHECK_STATE_CONTENT;
			return NO_REQUEST;
		}

		return GET_REQUEST;

	} else if (strncasecmp(text, hd_Connection.data, hd_Connection.len) == 0) {
		// 头部 Connection:
		text += hd_Connection.len;
		text += strspn(text, " ");						// 第一个非空字符
		if (strcasecmp(text, "keep-alive") == 0) {
			http_conn->http_linger = true;				// 保持连接
		}
	} else if (strncasecmp(text, hd_Content_Length.data, hd_Content_Length.len) == 0) {
		// 头部 Content-Length:
		text += hd_Content_Length.len;
		text += strspn(text, " ");						// 第一个非空字符
		http_conn->http_content_len = atol(text);		// 字符长度

	} else if (strncasecmp(text, hd_Host.data, hd_Host.len) == 0) {
		// 头部 Host:
		text += hd_Host.len;
		text += strspn(text, " ");						// 第一个非空字符
		http_conn->http_host = text;					// 主机名
	} else {
//		error_msg("ops! unknow header %s", text);
	}

	return NO_REQUEST;
}

// @note 暂时没有解析HTTP的消息体
HTTP_CODE http_conn_process_content(http_conn_t *http_conn, char *text)
{
	if (http_conn->rd_read_index >= (http_conn->http_content_len + http_conn->rd_check_index) ) {
		text[http_conn->http_content_len] = '\0';
		return GET_REQUEST;
	}
	return NO_REQUEST;
}

/**
 * @brief 有限状态机CHECK_STATE
 * @param http_conn http_conn_t结构指针
 * @return HTTP状态
 */
HTTP_CODE http_conn_process_read(http_conn_t *http_conn)
{
	LINE_STATE line_state = LINE_OK;
	HTTP_CODE ret = NO_REQUEST;
	char *text = NULL;

	// 读取正文阶段并且行读取成功  或者  当行读取成功（前一个条件为真，就不会检查后一个了）
	while ( ( (http_conn->check_state == CHECK_STATE_CONTENT) && (line_state == LINE_OK))
			|| ( (line_state = http_conn_parse_line(http_conn)) == LINE_OK) ) {

		text = http_conn_get_line(http_conn);
		http_conn->rd_start_line =  http_conn->rd_check_index;

#ifndef NDEBUG
		error_msg("get http: line: %s\n", text);
#endif

		switch (http_conn->check_state) {
			case CHECK_STATE_REQUESTLINE:			// 读取请求行
				{
					ret = http_conn_parse_request_line(http_conn ,text);
					if (ret == BAD_REQUEST) {
						return BAD_REQUEST;
					}
					break;
				}
			case CHECK_STATE_HEADER:				// 读取首部
				{
					ret = http_conn_parse_headers(http_conn, text);
					if (ret == GET_REQUEST) {
						return http_conn_do_request(http_conn);
					}
					line_state = LINE_OPEN;
					break;
				}
			case CHECK_STATE_CONTENT:				// 读取正文
				{
					ret = http_conn_process_content(http_conn, text);
					if (ret == GET_REQUEST) {
						return http_conn_do_request(http_conn);
					}
					line_state = LINE_OPEN;			// 还没读完
					break;
				}
			default:								// 状态出错了
				{
					return INTERNAL_ERROR;
				}
		}
	}
	return NO_REQUEST;
}

/**
 * @brief 根据请求做出响应
 * @param http_conn http_conn_t结构指针
 * @return HTTP状态
 */
HTTP_CODE http_conn_do_request(http_conn_t *http_conn)
{
	int fd;
	int len =  strlen(DOC_ROOT);

	strncpy(http_conn->http_filename, DOC_ROOT, len);
	strncpy(http_conn->http_filename + len, http_conn->http_url, FILENAME_LEN - len - 1);

	// 获取文件信息
	if (stat(http_conn->http_filename, &http_conn->http_file_stat) !=  0) {
		error_msg("File Not Found: %s", http_conn->http_filename);
		return NOT_FOUND;
	}

	// 获取文件权限信息
	if (!(http_conn->http_file_stat.st_mode & S_IROTH)) {
		error_msg("FORBIDDEN: %s", http_conn->http_filename);
		return FORBIDDEN;
	}

	// 判断是否为目录
	if (S_ISDIR(http_conn->http_file_stat.st_mode)) {
		error_msg("FORBIDDEN: %s", http_conn->http_filename);
		return FORBIDDEN;
	}

	// 打开文件
	if ( (fd = open(http_conn->http_filename, O_RDONLY)) < 0) {
		error_ret("open error");
		return FORBIDDEN;
	}

	// 将文件映射到内存
	if ((http_conn->http_file_address = (char *)mmap(0, http_conn->http_file_stat.st_size, PROT_READ,
					MAP_PRIVATE, fd, 0) ) == MAP_FAILED) {
		return FORBIDDEN;
	}

	close(fd);

	error_msg("File: %s", http_conn->http_filename);

	return FILE_REQUEST;
}

/**
 * @brief 解除映射
 * @param http_conn http_conn_t结构指针
 */
void http_conn_unmap(http_conn_t *http_conn)
{
	if (http_conn->http_file_address) {
		if (munmap(http_conn->http_file_address, http_conn->http_file_stat.st_size)  != 0) {
			fprintf(stderr, "munmap error: %s\n", strerror(errno));
		}
		http_conn->http_file_address = NULL;
	}
}

/**
 * @brief 写入HTTP响应
 * @param http_conn http_conn_t结构指针
 * @param epollfd epoll文件描述符
 * @return true 关闭连接；false 保持连接
 */
bool http_conn_write(http_conn_t *http_conn, int epollfd)
{
	int temp = 0;
	int bytes_have_send = 0;
	int bytes_to_send = http_conn->wr_length;

	if (bytes_to_send == 0) {
		epoll_mod(epollfd, http_conn->sockfd, EPOLLIN);
		http_conn_init_part(http_conn);
		return true;
	}

	while (true) {
		temp = writev(http_conn->sockfd,
				http_conn->http_iov, http_conn->http_iov_count);

		if (temp <= -1) {
			// 如果写缓冲没有空间，则等待下一轮EPOLLOUT事件。虽然在此期间，无服务无法立即接收到同一个客户的下一个请求，但这可以保证连接的完整性
			if (errno == EAGAIN) {
				epoll_mod(epollfd, http_conn->sockfd, EPOLLOUT);

				return true;
			}
			http_conn_unmap(http_conn);

			return false;
		}
		bytes_to_send -= temp;
		bytes_have_send += temp;

		// 需要发送的数据长度 <= 已发送的数据长度
		if (bytes_to_send <= bytes_have_send) {
			http_conn_unmap(http_conn);

			if (http_conn->http_linger) {
				http_conn_init_part(http_conn);		// 保持连接
				epoll_mod(epollfd, http_conn->sockfd, EPOLLIN);

				return false;
			} else {
				epoll_mod(epollfd, http_conn->sockfd, EPOLLIN);
				return false;
			}
		}
	}
	return true;
}

/**
 * @brief 往缓冲区中写入待发送数据
 * @param http_conn http_conn_t结构指针
 * @param format
 * @param ...
 * @return 若成功则返回true,若失败则返回false
 */
bool http_conn_add_response(http_conn_t *http_conn, const char *format, ... )
{
	int len;
	va_list arg_list;

	if (http_conn->wr_length >= WRITE_BUFFER_SIZE) {
		return false;
	}

	va_start(arg_list, format);
	len = vsnprintf(http_conn->write_buf + http_conn->wr_length,
			WRITE_BUFFER_SIZE - 1 - http_conn->wr_length, format, arg_list);
	if (len >= (WRITE_BUFFER_SIZE - 1 - http_conn->wr_length)) {
		return false;				// 超出缓冲区了
	}

	http_conn->wr_length += len;
	va_end(arg_list);

	return true;
}

/**
 * @brief 状态行
 * @param http_conn http_conn_t结构指针
 * @param status 状态码
 * @param title 原因短语
 * @return 若成功返回true,若失败则为false
 */
bool http_conn_add_status_line(http_conn_t *http_conn, int status, const char *title)
{
	return http_conn_add_response(http_conn, "%s %d %s\r\n", "HTTP/1.1", status, title);
}

/**
 * @brief 首部
 * @param http_conn http_conn_t结构指针
 * @param content_len 正文长度
 * @return 若成功则返回true,若失败则返回false
 */
bool http_conn_add_headers(http_conn_t *http_conn, int content_len)
{
	http_conn_add_content_length(http_conn, content_len);	// 首部长度
	http_conn_add_linger(http_conn);						// 是否保持连接
	http_conn_add_blank_line(http_conn);					// 空行

	return true;
}

/**
 * @brief 正文长度
 * @param http_conn http_conn_t结构指针
 * @param content_len 正文长度
 * @return 若成功则为true,若失败则为false
 */
bool http_conn_add_content_length(http_conn_t *http_conn, int content_len)
{
	return http_conn_add_response(http_conn, "Content-Length: %d\r\n", content_len);
}

/**
 * @brief 保持连接
 * @param http_conn http_conn_t结构指针
 * @return 若成功则为true,若失败则为false
 */
bool http_conn_add_linger(http_conn_t *http_conn)
{
	return http_conn_add_response(http_conn, "Connection: %s\r\n", (http_conn->http_linger == true) ?
			"keep-alive" : "close");
}

/**
 * @brief 添加空行
 * @param http_conn http_conn_t结构指针
 * @return 若成功则为true,若失败则为false
 */
bool http_conn_add_blank_line(http_conn_t *http_conn)
{
	return http_conn_add_response(http_conn, "%s", "\r\n");
}

/*
 * 添加正文
 */
bool http_conn_add_content(http_conn_t *http_conn, const char *content)
{
	return http_conn_add_response(http_conn, "%s", content);
}

http_status_t status_200 = {
	200,
	STRING("OK"),
	STRING("<html><body></body></html>")
};

http_status_t status_400 = {
	400,
	STRING("Bad Request"),
	STRING("Your request has bad syntax or is inherrently impossible to satisfy.\n")
};

http_status_t status_403 = {
	403,
	STRING("Forbidden"),
	STRING("You do not have permission to get file from this server.\n")
};

http_status_t status_404 = {
	404,
	STRING("Not Found"),
	STRING("The requested file was not found on the server.\n")
};

http_status_t status_500 = {
	500,
	STRING("Internal Server Error"),
	STRING("There was an unsual problem serving the requested file\n")
};

http_status_t status_501 = {
	501,
	STRING("Not Implemented"),
	STRING("The method not implement\n")
};

http_status_t status_505 = {
	505,
	STRING("HTTP Version Not Supported"),
	STRING("HTTP Version Not Supported\n")
};


/*
 * 根据服务器处理，生成HTTP响应报文
 */
bool http_conn_process_write(http_conn_t *http_conn, HTTP_CODE ret)
{
#ifndef NDBUGE
	error_msg("http_conn_process_write");
#endif
	switch(ret) {
		case INTERNAL_ERROR:			// 未知错误 500
			{
				http_conn_add_status_line(http_conn, status_500.status, status_500.reason.data);
				http_conn_add_headers(http_conn, status_500.content.len);
				if (!http_conn_add_content(http_conn, status_500.content.data)) {
					return false;
				}

				break;
			}
		case BAD_REQUEST:				// 错误的请求 400
			{
				http_conn_add_status_line(http_conn, status_400.status, status_400.reason.data);
				http_conn_add_headers(http_conn, status_400.content.len);
				if (!http_conn_add_content(http_conn, status_400.content.data)) {
					return false;
				}

				break;
			}
		case FORBIDDEN:					// 没有权限 403
			{
				http_conn_add_status_line(http_conn, status_403.status, status_403.reason.data);
				http_conn_add_headers(http_conn, status_403.content.len);
				if (!http_conn_add_content(http_conn, status_403.content.data)) {
					return false;
				}

				break;
			}
		case NOT_FOUND:					// 页面不存在 404
			{
				http_conn_add_status_line(http_conn, status_404.status, status_404.reason.data);
				http_conn_add_headers(http_conn, status_404.content.len);
				if (!http_conn_add_content(http_conn, status_404.content.data)) {
					return false;
				}

				break;
			}
		case NOT_IMPLEMENTED:				// 方法未实现 501
			{
				http_conn_add_status_line(http_conn, status_501.status, status_501.reason.data);
				http_conn_add_headers(http_conn, status_501.content.len);
				if (!http_conn_add_content(http_conn, status_501.content.data)) {
					return false;
				}

				break;
			}
		case FILE_REQUEST:					// 文件资源 200
			{
				http_conn_add_status_line(http_conn, status_200.status, status_200.reason.data);
				if (http_conn->http_file_stat.st_size != 0) {

					http_conn_add_headers(http_conn, http_conn->http_file_stat.st_size);
					http_conn->http_iov[0].iov_base = http_conn->write_buf;
					http_conn->http_iov[0].iov_len = http_conn->wr_length;
					http_conn->http_iov[1].iov_base = http_conn->http_file_address;
					http_conn->http_iov[1].iov_len = http_conn->http_file_stat.st_size;
					http_conn->http_iov_count = 2;
					return true;

				}else {

					http_conn_add_headers(http_conn, status_200.content.len);
					if (!http_conn_add_content(http_conn, status_200.content.data)) {
						return false;
					}
				}

				break;
			}
		case HTTP_VERSION_NOT_SUPPORTED:	// HTTP版本不支持 505
			{
				http_conn_add_status_line(http_conn, status_505.status, status_505.reason.data);
				http_conn_add_headers(http_conn, status_505.content.len);
				if (!http_conn_add_content(http_conn, status_505.content.data)) {
					return false;
				}

				break;
			}
		default:
			{
				return false;
			}
	}

	http_conn->http_iov[0].iov_base = http_conn->write_buf;
	http_conn->http_iov[0].iov_len = http_conn->wr_length;
	http_conn->http_iov_count = 1;

	return true;
}

 /**
  * @brief
  * @param http_conn
  * @param epollfd
  */
void http_conn_process(http_conn_t *http_conn, int epollfd)
{
	bool write_ret;
	HTTP_CODE  read_ret;

	// 处理读
	read_ret = http_conn_process_read(http_conn);

	if (read_ret == NO_REQUEST) {
		epoll_mod(epollfd, http_conn->sockfd, EPOLLIN);
		return;
	}

	// 处理写
	write_ret = http_conn_process_write(http_conn, read_ret);

	if (!write_ret) {
		// 关闭连接
		http_conn_close_conn(http_conn, epollfd, true);
	}
	epoll_mod(epollfd, http_conn->sockfd, EPOLLOUT);
}
